# -*- coding:utf-8 -*-
"""Created By Corvo"""

import random

from mod.common.minecraftEnum import ActorDamageCause

from ...mdk.common.system.base import *
from ...mdk.common.utils.misc import Misc
from ...mdk.module.mob.parts.entity import ModuleEntityPreset, EntityServerPresetSkillBase
from ...mdk.server.entity import *
from ...modCommon.modEnum import *


class BeastMobEntityBase(EntityServerPresetSkillBase):
    """模组生物基类"""

    def SetPlayMusic(self, name):
        self.BroadcastToAllClient(ServerEvent.RequestPlayMusicEvent, {
            "entityId": self.id,
            "name": name
        })

    def OnWillDie(self, args):
        super(BeastMobEntityBase, self).OnWillDie(args)
        # if self.type_str in ModMergeData.ChaosEntity and random.random() <= 0.8:
        #     self.SpawnItem(self.Item.Create(ModItem.crystal, count=random.randint(1, 2)))

    def GetPlayerIns(self, playerId):
        pack = {"playerId": playerId}
        self.BroadcastEvent(ServerEvent.RequestGetBeastPlayerInsEvent, pack)
        ins = pack.get("ins")
        return ins


"""精灵相关"""


class SpriteEntityBase(BeastMobEntityBase):
    """精灵生物基类"""

    def __init__(self, entityId):
        super(SpriteEntityBase, self).__init__(entityId)
        self.skill_module = self.ModuleSystem.GetModule("skill")  # type: MDKConfig.GetPresetModule().SkillModuleServer
        self.ResetTrackEntity()

        pack = {"active": False}
        self.BroadcastEvent(ServerEvent.RequestCheckCrazyNightEvent, pack)
        self._crazy = pack.get("active")
        if self._crazy:
            self.TriggerCustomEvent("event:get_crazy")

    def OnDestroy(self):
        del self.skill_module
        super(SpriteEntityBase, self).OnDestroy()

    def ConfigEvent(self):
        super(SpriteEntityBase, self).ConfigEvent()
        self.serverEvent[ServerEvent.OnActiveCrazyNightEvent] = self.OnActiveCrazyNightEvent
        self.serverEvent[ServerEvent.OnDeactiveCrazyNightEvent] = self.OnDeactiveCrazyNightEvent

    def SetInstantDamage(self, victimId, damage, **kwargs):
        kwargs["cause"] = ActorDamageCause.NONE
        super(SpriteEntityBase, self).SetInstantDamage(victimId, damage, **kwargs)

    def GetConfig(self):
        return ModMergeData.MobAttrConfig.get(self.type_str, {})

    def ConfigTriggerMap(self):
        return {
            "event:active_delayed_attack": self.TriggerAttack,
            "event:active_attack": self.ActiveAttack,
            "event:reset_skill": self.ResetSkill,
            "event:active_death": self.ActiveDeath,
            "event:get_captured": self.ActiveGetCaptured
        }

    def TriggerAttack(self):
        if not self.IsSkillOnCd("attack"):
            self.TriggerCustomEvent("event:active_attack")

    def ActiveGetCaptured(self):
        """触发被捕捉"""
        # 播放动画
        self.AddEffect(GameEffect.MOVEMENT_SLOWDOWN, 5, 9, True)
        self.BroadcastToAllClient(ServerEvent.RequestSetMolangEvent, {
            "playerId": self.id,
            "config": [
                {
                    "molang": GameMolang.capture,
                    "value": 1.0
                }
            ]
        })
        self.BroadcastEvent("OnBeastEntityGetCapturedEvent", {"entityId": self.id})
        self.Destroy(1.2)

    def ActiveAttack(self):
        super(SpriteEntityBase, self).ActiveAttack()
        self.SetPlayMusic("beast.%s_attack" % self.type_str.split(":")[-1])

    # -----------------------------------------------------------------------------------

    def OnActiveCrazyNightEvent(self, _):
        max_health = self.GetMaxHealth()
        self.SetMaxHealth(int(max_health + 10))
        self.AddHealth(10)

        if not self._crazy:
            self.BroadcastToAllClient(ServerEvent.RequestBindEntityEffectEvent, {
                "entityId": self.id,
                "path": ["effects/jl_kbh_1.json", "effects/jl_kbh_2.json"]
            })
        self._crazy = True
        self.TriggerCustomEvent("event:get_crazy")

    def OnDeactiveCrazyNightEvent(self, _):
        max_health = self.GetMaxHealth()
        self.SetMaxHealth(int(max_health - 10))
        if self._crazy:
            self.BroadcastToAllClient(ServerEvent.RequestUnBindEntityEffectEvent, {
                "entityId": self.id
            })
        self._crazy = False
        self.TriggerCustomEvent("event:reset_crazy")

    def OnWillDie(self, args):
        super(SpriteEntityBase, self).OnWillDie(args)

    def OnDealDamage(self, args):
        super(SpriteEntityBase, self).OnDealDamage(args)

        if args["cause"] == ActorDamageCause.EntityAttack:
            args["damage"] = 0
            args["knock"] = False
            args["ignite"] = False
            return
        if self._crazy:
            args["damage"] += 2


class GoldSpriteEntityBase(SpriteEntityBase):
    """金属性生物"""

    def OnActuallyDealDamage(self, args):
        super(GoldSpriteEntityBase, self).OnActuallyDealDamage(args)
        entityId = args["entityId"]
        self.CastEffect(entityId, GameEffect.WEAKNESS, 3, 1)


class WoodSpriteEntityBase(SpriteEntityBase):
    """木属性生物"""

    def OnActuallyDealDamage(self, args):
        super(WoodSpriteEntityBase, self).OnActuallyDealDamage(args)
        entityId = args["entityId"]
        self.CastEffect(entityId, GameEffect.POISON, 3, 1)


class WaterSpriteEntityBase(SpriteEntityBase):
    """水属性生物"""

    def OnActuallyDealDamage(self, args):
        super(WaterSpriteEntityBase, self).OnActuallyDealDamage(args)
        entityId = args["entityId"]
        self.CastEffect(entityId, GameEffect.LEVITATION, 2, 1)


class FireSpriteEntityBase(SpriteEntityBase):
    """火属性生物"""

    def OnActuallyDealDamage(self, args):
        super(FireSpriteEntityBase, self).OnActuallyDealDamage(args)
        entityId = args["entityId"]
        self.SetEntityFire(entityId, 3)


class DirtSpriteEntityBase(SpriteEntityBase):
    """土属性生物"""

    def OnActuallyDealDamage(self, args):
        super(DirtSpriteEntityBase, self).OnActuallyDealDamage(args)
        entityId = args["entityId"]
        self.CastEffect(entityId, GameEffect.MOVEMENT_SLOWDOWN, 3, 1)


"""灵兽相关"""


class CombineEntityBase(BeastMobEntityBase):
    """融合生物基类"""

    def __init__(self, entityId):
        super(CombineEntityBase, self).__init__(entityId)
        self._owner_player = self.GetTameOwner()
        self.ResetTrackEntity()
        # 回收机制：5s内无受伤
        self.invincible = False
        self.retrieve_sec = 0
        self.on_skill_anim = False
        # 克制元素
        self.conquer_element = ElementType.ConquerMap[ModMergeData.MobAttrConfig[self.type_str]["element"]]
        # 检测是否在竞技场
        self.game_comp.AddTimer(0.1, self.CheckOnArena)
        # version 6.0 受伤伤害倍率
        self.damage_impact = 1.0

    def ConfigEvent(self):
        super(CombineEntityBase, self).ConfigEvent()
        self.clientEvent[UIEvent.RequestActiveCombineMobSkillEvent] = self.RequestActiveCombineMobSkillEvent
        self.serverEvent[ServerEvent.RequestRetrieveCombineEntityEvent] = self.RequestRetrieveCombineEntityEvent

    @staticmethod
    def SetProjectileBindEffect_v13(projectile_id, anim, path="models/bind/v13_bind.json", destroy=5.0):
        particle = ParticleEntity(projectile_id)
        particle.SetModel("base_entity")
        particle.SetFixBind(path, anim).Play()
        # particle.SetTrackEntity(projectile_id, lifeTime=int(destroy * 30))
        particle.Destroy(destroy)
        return particle

    def SetBindEffect_v13(self, anim, path="models/bind/v13_bind.json", binder=None, destroy=5.0, pos=None, **kwargs):
        spawn_pos = pos if pos else self.foot_pos
        model = kwargs.get("model", "base_entity")
        particle = self.AddParticleEntity(GameEntity.detector, pos=spawn_pos).SetModel(model)
        if binder:
            particle.SetBinder(binder)
        particle.SetFixBind(path, anim).Play()
        if kwargs.get("trackId"):
            particle.SetTrackEntity(kwargs["trackId"], forward=kwargs.get("forward", 0))
        particle.Destroy(destroy)
        return particle

    # -----------------------------------------------------------------------------------

    def CheckOnArena(self):
        """在竞技场野生生成将被清除"""
        if self.GetTameOwner():
            return

        if self.dim == GameDimension.Arena:
            self.Destroy()

    # -----------------------------------------------------------------------------------

    def SetInstantDamage(self, victimId, damage, **kwargs):
        kwargs["cause"] = ActorDamageCause.NONE
        super(CombineEntityBase, self).SetInstantDamage(victimId, damage, **kwargs)

    def GetConfig(self):
        return ModMergeData.MobAttrConfig.get(self.type_str, {})

    def TriggerAttack(self):
        if not self.IsSkillOnCd("sk1"):
            self.TriggerCustomEvent("event:active_skill1")
        elif not self.IsSkillOnCd("sk2"):
            self.TriggerCustomEvent("event:active_skill2")
        elif not self.IsSkillOnCd("attack"):
            self.TriggerCustomEvent("event:active_attack")

    def ConfigTriggerMap(self):
        return {
            "event:active_delayed_attack": self.TriggerAttack,
            "event:active_tame": self.ActiveTame,
            "event:active_attack": self.ActiveAttack,
            "event:active_skill1": self.ActiveSkill1,
            "event:active_skill2": self.ActiveSkill2,
            "event:active_skill3": self.ActiveSkill3,
            "event:reset_skill": self.ResetSkill,
            "event:active_death": self.ActiveDeath
        }

    def ActiveAttack(self):
        super(CombineEntityBase, self).ActiveAttack()
        self.on_skill_anim = True
        if self._owner_player:
            player = self.GetPlayerIns(self._owner_player)
            if player.combine_entity != self.id:
                return
            anim_time = self.GetConfig()["attack"]["anim_time"]
            skill_cd = self.GetConfig()["attack"]["cd"]
            self.NotifyToClient(self._owner_player, ServerEvent.RequestSetSkillCDEvent, {
                "slot": "button0",
                "anim_time": anim_time,
                "cd": skill_cd
            })

    def ActiveSkill1(self):
        super(CombineEntityBase, self).ActiveSkill1()
        self.on_skill_anim = True
        if self._owner_player:
            player = self.GetPlayerIns(self._owner_player)
            if player.combine_entity != self.id:
                return
            anim_time = self.GetConfig()["sk1"]["anim_time"]
            skill_cd = self.GetConfig()["sk1"]["cd"]
            self.NotifyToClient(self._owner_player, ServerEvent.RequestSetSkillCDEvent, {
                "slot": "button1",
                "anim_time": anim_time,
                "cd": skill_cd
            })

    def ActiveSkill2(self):
        super(CombineEntityBase, self).ActiveSkill2()
        self.on_skill_anim = True
        if self._owner_player:
            player = self.GetPlayerIns(self._owner_player)
            if player.combine_entity != self.id:
                return
            anim_time = self.GetConfig()["sk2"]["anim_time"]
            skill_cd = self.GetConfig()["sk2"]["cd"]
            self.NotifyToClient(self._owner_player, ServerEvent.RequestSetSkillCDEvent, {
                "slot": "button2",
                "anim_time": anim_time,
                "cd": skill_cd
            })

    def ActiveSkill3(self):
        super(CombineEntityBase, self).ActiveSkill3()
        self.on_skill_anim = True
        if self._owner_player:
            player = self.GetPlayerIns(self._owner_player)
            if player.combine_entity != self.id:
                return
            anim_time = self.GetConfig()["sk3"]["anim_time"]
            skill_cd = self.GetConfig()["sk3"]["cd"]
            self.NotifyToClient(self._owner_player, "RequestSetSkillCDExtraEvent", {
                "slot": "button3",
                "anim_time": anim_time,
                "cd": skill_cd
            })

    def OnActuallyDealDamage(self, args):
        super(CombineEntityBase, self).OnActuallyDealDamage(args)
        if args["damage"] > 0:
            self.retrieve_sec = 5
            entityId = args["entityId"]
            engine_type = self.GetEngineTypeStr(entityId)
            if engine_type not in ModMergeData.MobAttrConfig:
                return
            # version 9.0 明灵生物不受属性加成
            if engine_type in ModMergeData.MachineEntity:
                return
            if self.conquer_element == ModMergeData.MobAttrConfig[engine_type]["element"]:
                args["damage"] = int(args["damage"] * 1.1)

    def ActiveTame(self):
        if self.GetTameOwner():
            self._owner_player = self.GetTameOwner()
            return

        def active():
            yield 3
            self._owner_player = self.GetTameOwner()

        self.StartCoroutine(active)

    # -----------------------------------------------------------------------------------

    def OnFinalDelHealth(self, value):
        self.retrieve_sec = 5

    def OnDealDamage(self, args):
        if args["cause"] == ActorDamageCause.EntityAttack or args["entityId"] == self._owner_player:
            args["damage"] = 0
            args["knock"] = False
            args["ignite"] = False

    def OnDeath(self, attacker):
        config = self.GetConfig()
        if not self.GetTameOwner():
            # 战利品
            for entity in config["element_mob"]:
                item_name = entity.replace("beast:", "beast:essential_")
                if self.Item.LookupItemByName(item_name):
                    self.Item.SpawnAtPosByName(item_name, pos=self.foot_pos, dim=self.dim)
            return
        self.NotifyToClient(self._owner_player, ServerEvent.RequestBindLockAimEntityEvent, {
            "show": False
        })
        for mob_type in config["element_mob"]:
            self.AddEntity(mob_type, self.foot_pos, self.dim)

    def OnKillEntity(self, victimId):
        if not self._owner_player:
            return
        player = self.GetPlayerIns(self._owner_player)
        engine_type = self.GetEngineTypeStr(victimId)
        if engine_type in ModMergeData.BookShowEntity:
            if engine_type not in player.book_unlock:
                player.AddUnlockMobType(engine_type)

    def OnUpdateSecond(self):
        if self.retrieve_sec > 0:
            self.retrieve_sec -= 1

    def TriggerResetSkill(self):
        super(CombineEntityBase, self).TriggerResetSkill()
        self.on_skill_anim = False

    def OnTookDamage(self, args):
        super(CombineEntityBase, self).OnTookDamage(args)
        damage = args["damage"]
        if self.damage_impact != 1.0:
            args["damage"] = int(damage * self.damage_impact)
        if self.invincible:
            args["damage"] = 0
            args["knock"] = False

    # -----------------------------------------------------------------------------------

    @check_entity_id
    def RequestActiveCombineMobSkillEvent(self, args):
        playerId = args["playerId"]
        if not self._owner_player:
            self._owner_player = playerId
        slot = args["slot"]
        if slot == "button1" and not self.IsSkillOnCd("sk1"):
            self.TriggerCustomEvent("event:active_skill1")
        elif slot == "button2" and not self.IsSkillOnCd("sk2"):
            self.TriggerCustomEvent("event:active_skill2")
        elif slot == "button3" and not self.IsSkillOnCd("sk3"):
            self.TriggerCustomEvent("event:active_skill3")
        elif slot == "button0" and not self.IsSkillOnCd("attack"):
            self.TriggerCustomEvent("event:active_attack")

    @check_entity_id
    def RequestRetrieveCombineEntityEvent(self, args):
        args["isPass"] = self.retrieve_sec <= 0 and not self.on_skill_anim


class CombineEntityBaseNew(CombineEntityBase):

    def __init__(self, entityId):
        super(CombineEntityBaseNew, self).__init__(entityId)
        self._entity_gen = {}
        self.skill_module = self.ModuleSystem.GetModule("skill")  # type: MDKConfig.GetPresetModule().SkillModuleServer

    def OnDestroy(self):
        self._DestroyAllGen()
        del self.skill_module
        super(CombineEntityBaseNew, self).OnDestroy()

    def _DestroyAllGen(self):
        if not self._entity_gen:
            return
        for gen in self._entity_gen.values():
            if isinstance(gen, list):
                for _gen in gen:
                    self.StopCoroutine(_gen)
                continue
            self.StopCoroutine(gen)
        self._entity_gen.clear()

    def OnEntityRemove(self):
        self._DestroyAllGen()

    def OnDeath(self, attacker):
        self._DestroyAllGen()
        super(CombineEntityBaseNew, self).OnDeath(attacker)

    def OnSkillFinished(self, genId=None):
        self._entity_gen.pop(genId, None)
        self.TriggerResetSkill()

    # -----------------------------------------------------------------------------------

    def SetBindEffect(self, anim, path="models/bind/v1_bind.json", binder=None, destroy=5.0, pos=None, **kwargs):
        spawn_pos = pos if pos else self.foot_pos
        model = kwargs.get("model", "base_entity")
        particle = self.AddParticleEntity(GameEntity.detector, pos=spawn_pos).SetModel(model)
        if binder:
            particle.SetBinder(binder)
        particle.SetFixBind(path, anim).Play()
        if kwargs.get("trackId"):
            particle.SetTrackEntity(kwargs["trackId"], forward=kwargs.get("forward", 0))
        particle.Destroy(destroy)
        return particle

    def SetProjectileBindEffect(self, anim, path="models/bind/v1_bind.json", binder=None, destroy=5.0, pos=None):
        spawn_pos = pos if pos else self.foot_pos
        particle = self.AddParticleEntity(GameEntity.projectile, pos=spawn_pos).SetModel("base_projectile")
        if binder:
            particle.SetBinder(binder)
        particle.SetFixBind(path, anim).Play()
        particle.Destroy(destroy)
        return particle

    # -----------------------------------------------------------------------------------

    def GetMoveForwardGen(self, duration, power=1.0, height=0.0, horizon=True):
        """获得向前位移的生成器"""
        yield 0
        drt = self.GetDrt(horizon)
        actionComp = self.comp_factory.CreateAction(self.id)
        for _ in xrange(duration):
            actionComp.SetMobKnockback(drt[0], drt[2], power, height, height)
            yield 1

    def GetFacingEntityGen(self, duration, targetId):
        yield 0
        if self.game_comp.IsEntityAlive(targetId):
            for _ in xrange(duration):
                self.SetFacingEntity(targetId)
                yield 1
